home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-06-24 | 25.2 KB | 594 lines | [TEXT/MMCC] |
- //==============================================================================================\\
- // ------------------------------------------------------------------------------- \\
- // MGWSound1.c version 1.0.0 copyright © 1993…1995 Jamie McCornack, john calhoun \\
- // ------------------------------------------------------------------------------- \\
- // Sound management utilities for Macintosh GameWriter 1.0.0, a training program… \\
- // …for beginning Mac game programmers. MGW1 includes MGWExterns1.h, MGWUtilities1.c,… \\
- // …MGWSound1.c, MGWGraphics1.c, MGWGraphicsBWLite1.c, HelloWorld.rsrc and an assortment… \\
- // …of demo programs; projects HelloWorld1.π etc. and source code files HelloWorld1.c etc. \\
- // A tutorial is available in Tricks of the Mac Game Programming Gurus, published… \\
- // …by Hayden Books, August 1995. \\
- // \\
- // This code is offered by the copyright holders for no fee and for whatever use… \\
- // …you care to make of it, but we do hope you remember where it came from. \\
- // \\
- // Please send bug reports to MacGameDev at America OnLine. macgamedev@aol.com \\
- // Suggestions and observations are also appreciated. \\
- // Updates and upgrades will be available now and then from the above e-mail address. \\
- //==============================================================================================\\
-
- #include "MGWExterns1.h"
- #include <Sound.h>
-
- // Here is the constant that the SoundUnit uses internally
- #define kOurCallBack 911 // This arbitrary number will identify a call back as our own.
-
- // Here is the "global" variable used by the sound routines.
- Boolean gUserWantsSound; // A user-set variable used for turning on/off all sounds.
-
- // Here are the internal variables used by MGWSound1.c. If you use the proper calls…
- // …(see the routines in MGWExterns.h) you should never need to access these externally.
- SndChannelPtr soundChannel; // This is the sound channel all our routines use
- short soundPriority; // This is the priority of the sound currently playing
- Boolean canUseSound; // Is FALSE if we can't use sound on this Mac
-
-
- //------------------------------------------------------------ Prototypes
-
- void FlushSoundNow (void);
- void FlushSoundSoon (void);
- pascal void SoundCallBack(SndChannelPtr chan, SndCommand theCommand);
- OSErr InstallCallBack(void);
-
-
- //============================================================== Functions
-
- //-------------------------------------------------------------- CloseDownSound
-
- // Call this function to dispose of the sound channel when switching out or quitting.
- // Otherwise, sound may be disabled for the next program you run.
-
- OSErr CloseDownSound(void)
- {
- OSErr theErr;
-
- theErr = noErr; // Assume no error
-
- if (soundChannel != nil) {
- theErr = SndDisposeChannel(soundChannel, TRUE);
- soundChannel = nil; // Set the variable to nil now
- }
-
- soundPriority = kNoSoundPlaying; // soundPriority needs to reflect no sound playing
-
- return(theErr);
- }
-
- //-------------------------------------------------------------- InitializeForSound
-
- // This procedure initializes all variables used by the sound unit routines.
- // It also does a check using SysEnvirons() to determine if sound will run at all.
- // You should have your program call this function only once at start up!
-
- void InitializeForSound()
- {
- SysEnvRec thisWorld;
-
- gUserWantsSound = TRUE;
-
- soundChannel = nil; // Initialize our sound channel to nil (very important).
- soundPriority = kNoSoundPlaying; // Indicate no sound is playing.
-
- SysEnvirons(1, &thisWorld); // Call on SysEnvirons() to determine if we can play
- // sound on this particular Mac and System version.
-
- // Anything before the Mac II may not work for these routines.
- // Anything before System 6.0.5 may not work for these routines.
- canUseSound = ((thisWorld.machineType >= envMacII) && (thisWorld.systemVersion >= 0x0605));
- }
-
- //-------------------------------------------------------------- LoadASound
-
- // Call this function to load a specific sound (by passing the ID of the 'snd ' resource
- // in your program). It will load it, move it high in memory, and lock it. It will
- // return FALSE if there was an error. Errors could be due to low memory or because
- // the ID you specified was not found.
-
- Boolean LoadASound(short soundID)
- {
- Handle theSoundHandle;
- Boolean noProblem;
-
- theSoundHandle = Get1Resource('snd ', soundID);
-
- if (theSoundHandle == nil) { // If the memory manager didn't assign a handle to
- noProblem = FALSE; // theSoundHandle, then there was an error.
- } else {
- noProblem = TRUE;
- MoveHHi(theSoundHandle);
- HLock(theSoundHandle);
- }
-
- return(noProblem); // Report back to calling routine.
- }
-
- //-------------------------------------------------------------- LoadARangeOfSounds
-
- // This function loads a range of sounds (say from ID = 2000 to ID = 2014). You must
- // be quite sure however that all those sounds exist. If anyone of them cannot be
- // found (or if memory is too low to permit them all to load) the function will return
- // FALSE indicating a failure to load one or more of the specified sounds. Note that
- // this function just makes subsequent calls to the above function.
-
- Boolean LoadARangeOfSounds(short firstID, short lastID)
- {
- short i;
- Boolean noProblem;
-
- noProblem = TRUE; // Start by assuming there won't be an error.
-
- for (i = firstID; i <= lastID; i++) {
- if (! LoadASound(i)) { // If LoadASound returned FALSE on any sound
- noProblem = FALSE; // in the range, there was a problem.
- }
- }
-
- return(noProblem); // Report back to calling routine.
- }
-
- //-------------------------------------------------------------- LoadAllSounds
-
- // This function takes a different strategy for loading sounds. It uses the Resource
- // Manager routines to indicate the number of 'snd ' resources in your program
- // and then loads every one of them in the order that they appear in the resource fork.
-
- Boolean LoadAllSounds()
- {
- Handle theSoundHandle;
- short numberOfSounds, i;
- Boolean noProblem;
-
- noProblem = TRUE; // Start by assuming there won't be an error.
-
- numberOfSounds = Count1Resources('snd '); // Determine # of sounds.
-
- for (i = 1; i <= numberOfSounds; i++) { // Loop through all sounds.
- theSoundHandle = Get1IndResource('snd ', i); // Get each sound.
-
- if (theSoundHandle != nil) { // Did we get a valid sound?
- MoveHHi(theSoundHandle); // Move sound handle high in memory
- HLock(theSoundHandle); // And lock it!
- } else {
- noProblem = FALSE; // But if we didn't get a valid sound, then we failed.
- }
- }
-
- return(noProblem); // Report back to calling routine.
- }
-
- //-------------------------------------------------------------- ReleaseASound
-
- // Call this function to unlock a specific sound (by passing the ID of the 'snd ' resource
- // in your program). Unlocking allows the memory manager to move or purge if needed.
- // It will return FALSE if there was an error. Errors could be due to low memory (since it
- // will load first if the resource is not in memory) or because the ID you specified was not found.
-
- Boolean ReleaseASound(short soundID)
- {
- Handle theSoundHandle;
- Boolean noProblem;
-
- theSoundHandle = Get1Resource('snd ', soundID); // Finds sound handle in memory if it's loaded,
- // otherwise loads it, so HUnlock will have a valid
- // block to unlock.
- if (theSoundHandle == nil) {
- noProblem = FALSE;
- } else {
- noProblem = TRUE;
- HUnlock(theSoundHandle); // Unlocks block, so Memory Manager can move or
- } // purge it as needed.
-
- return(noProblem); // Report back to calling routine.
- }
-
- //-------------------------------------------------------------- ReleaseARangeOfSounds
-
- // This function unlocks a range of sounds (say from ID = 2000 to ID = 2014). You must
- // be quite sure that all those sounds exist, though they need not be loaded into memory.
- // If anyone of them cannot be found (or if memory is too low to permit one to load) the function
- // will return FALSE indicating a failure to unlock one or more of the specified sounds.
- // Note that this function just makes subsequent calls to the above function.
-
- Boolean ReleaseARangeOfSounds(short firstID, short lastID)
- {
- short i;
- Boolean noProblem;
-
- noProblem = TRUE; // Start by assuming there won't be an error.
-
- for (i = firstID; i <= lastID; i++) {
- if (! ReleaseASound(i)) { // If ReleaseASound returned FALSE on any sound
- noProblem = FALSE; // in the range, there was a problem.
- }
- }
-
- return(noProblem); // Report back to calling routine.
- }
-
- //-------------------------------------------------------------- ReleaseAllSounds
-
- // This function takes a different strategy for unlocking sounds. It uses a Resource
- // Manager routine to indicate the number of 'snd ' resources in your program
- // and then unlocks every one of them in the order that they appear in the resource fork.
-
- Boolean ReleaseAllSounds()
- {
- Handle theSoundHandle;
- short numberOfSounds, i;
- Boolean noProblem;
-
- noProblem = TRUE; // Start by assuming there won't be a problem.
-
- numberOfSounds = Count1Resources('snd '); // Determine # of sounds.
-
- for (i = 1; i <= numberOfSounds; i++) { // Loop through all sounds.
- theSoundHandle = Get1IndResource('snd ', i);// Get each sound.
-
- if (theSoundHandle != nil) { // Did we get a valid sound?
- HUnlock(theSoundHandle); // And unlock it!
- } else {
- noProblem = FALSE; // But if we didn't get a valid sound, then we failed.
- }
- }
-
- return(noProblem); // Report back to calling routine.
- }
-
- //-------------------------------------------------------------- SoundCallBack
-
- // This procedure is never called from within the program. The Mac will call this
- // procedure when the Sound Manager gets to a call back command in the sound queue.
- // Because this procedure gets called at interrupt time. You cannot call any routine
- // that moves memory or creates it! No GetResource(), NewHandle(), etc.
- // Furthermore, this procedure must always be present in memory! For this to be
- // guaranteed, you should make sure the routine is in the Main segment of your
- // program. Put the whole SoundUnit in the Main memory segment in order to be safe.
- // This procedure sets soundPriority to zero, so other routines
- // can verify that a sound is no longer playing.
-
- // Toolbox callback routines *must* be declared pascal
- pascal void SoundCallBack(SndChannelPtr chan, SndCommand theCommand)
- {
- long theA5;
-
- if (theCommand.param1 == kOurCallBack) { // Make sure it's our callBack.
- theA5 = SetA5(theCommand.param2); // The A5 when InstallCallBack Routine was called.
- soundPriority = kNoSoundPlaying; // Let the program know the sound is done.
- theA5 = SetA5(theA5); // Restore the original A5. See IM6, 22-79.
- }
- }
-
- //-------------------------------------------------------------- InstallCallBack
-
- // This function also is only used internally by the SoundUnit. It is called right after
- // a sound is played asynchronously. It will install the callBack command into the
- // sound queue so that after a sound finishes playing, our call back routine is called
- // and we can be alerted to the fact that a sound has finished playing. This function
- // will return any error encountered.
-
- OSErr InstallCallBack()
- {
- SndCommand theCommand;
-
- theCommand.cmd = callBackCmd; // Set up the call back command.
- theCommand.param1 = kOurCallBack; // Flag this call back command as our own.
- theCommand.param2 = SetCurrentA5();
- // Load the command into our sound channel.
- return(SndDoCommand(soundChannel, &theCommand, FALSE));
- }
-
- //-------------------------------------------------------------- IsSoundOn
-
- // This function can be called to determine if sound is both desired and possible.
-
- Boolean IsSoundOn()
- {
- return (canUseSound && gUserWantsSound);
- }
-
- //-------------------------------------------------------------- ASoundIsPlaying
-
- // A simple utility function that returns TRUE is a sound is playing or FALSE if not.
- // This is how you can externally determine if a sound is playing.
-
- Boolean ASoundIsPlaying()
- {
- return(soundPriority != kNoSoundPlaying);
- }
-
- //-------------------------------------------------------------- SoundPriorityPlaying
-
- // A simple utility function that returns an integer indicating the priority of the
- // current sound playing. It will return zero (kNoSoundPlaying) if that is the case.
- // This is how you can externally check on the state of the variable 'soundPriority'.
-
- short SoundPriorityPlaying()
- {
- return(soundPriority);
- }
-
- //-------------------------------------------------------------- ThisMacCanPlaySounds
-
- // This utility allows the rest of your program access to the 'canUseSound' variable
- // that the SoundUnit uses. This function will return TRUE if it is possible to play
- // sound on this Mac or FALSE if it is not. As an example of this routines use, you
- // may have a menu item called 'Sound On' that the user can check or uncheck in
- // order to turn sounds in the game on or off. If however they are running on a Mac
- // where it is impossible to play sounds, you would probably rather gray-out the menu
- // item entirely. A call to the below function will let you know if sound is even an option.
-
- Boolean ThisMacCanPlaySounds()
- {
- return(canUseSound);
- }
-
-
- //-------------------------------------------------------------- FlushSoundNow
-
- // This routine is called internally to stop any sound currently playing and empty the…
- // …command queu of all upcoming sounds and commands.
-
- void FlushSoundNow (void)
- {
- SndCommand thisCommand;
- OSErr thisErr;
-
- thisCommand.cmd = flushCmd; // Flush any upcoming commands from the command queu.
- thisCommand.param1 = 0; // This command ignores these parameters.
- thisCommand.param2 = 0L;
- thisErr = SndDoImmediate(soundChannel, &thisCommand);
-
- thisCommand.cmd = quietCmd; // Send quietCmd to stop any current sound.
- thisCommand.param1 = 0; // This command ignores these parameters.
- thisCommand.param2 = 0L;
- thisErr = SndDoImmediate(soundChannel, &thisCommand);
- }
-
- //-------------------------------------------------------------- FlushSoundSoon
-
- // This routine is called internally to empty the command queu of all upcoming sounds…
- // …and commands, but leave the current sound playing.
-
- void FlushSoundSoon (void)
- {
- SndCommand thisCommand;
- OSErr thisErr;
-
- thisCommand.cmd = flushCmd; // Flush any upcoming commands from the command queu.
- thisCommand.param1 = 0; // This command ignores these parameters.
- thisCommand.param2 = 0L;
- thisErr = SndDoImmediate(soundChannel, &thisCommand);
- }
-
- // =========================================================================================\\
- // The Play…Sound routines \\
- // =========================================================================================\\
- // Here is where the results come from. At last, some routines that make sounds come out. \\
- // =========================================================================================\\
-
- //-------------------------------------------------------------- PlayASound
-
- // Here we are. This is the work horse routine used by the game to play sounds
- // asynchronously. After starting the sound playing, control is…
- // immediately returned to our program. This allows the action to continue! You need
- // simply specify the ID of the sound you want to play as well as the priority at which
- // you want to play the sound (1 - 100). If a sound of higher priority is already playing,
- // then the sound will not be played.
-
- OSErr PlayASound(short soundID, short priority)
- {
- OSErr theErr;
- Handle theSoundHandle;
-
- if (! IsSoundOn()) return; // Exit if we are not to play sounds.
-
- if (priority < soundPriority) return; // Exit if a higher priority sound is playing.
-
- theSoundHandle = Get1Resource('snd ', soundID); // Get the sound (only from our resource fork).
- if (theSoundHandle == nil) return; // Exit if we couldn't get the sound.
-
- if (! soundChannel == nil) // If soundChannel is open, flush out…
- FlushSoundNow(); // …any commands in the soundChannel queu,…
- // …and quiet the sound currently playing.
- else
- { // Create our sound channel.
- theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)&SoundCallBack);
- if (theErr != noErr) return; // Exit if that failed.
- }
- // Play the sound asynchronously.
- theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, TRUE);
- if (theErr != noErr) return; // And exit if that failed.
-
- soundPriority = priority; // Establish globally the priority of the sound playing.
-
- InstallCallBack(); // Lastly, queue up a callBackCmd to notify us…
- // when the sound has finished playing.
- return(theErr);
- }
-
-
- //-------------------------------------------------------------- PlaySynchSound
-
- // If you call this routine, the sound will be played and your program will wait here
- // until the sound is finished (playing synchronously). Like the above routine, a sound
- // priority must be passed in as well as the ID of the sound to be played synchronously.
- // To override any other sound playing, pass it kHighestPriority for the priority.
-
- OSErr PlaySynchSound(short soundID, short priority)
- {
- OSErr theErr;
- Handle theSoundHandle;
-
- if (! IsSoundOn()) return; // Exit if we are not to play sounds.
-
- if (priority < soundPriority) return; // Exit if a higher priority sound is playing.
-
- theSoundHandle = Get1Resource('snd ', soundID); // Get the sound (only from our resource fork).
- if (theSoundHandle == nil) return; // Exit if we couldn't get the sound.
-
- if (! soundChannel == nil) // If soundChannel is open, flush out…
- FlushSoundNow(); // …any commands in the soundChannel queu,…
- // …and quiet the sound currently playing.
- else
- { // Create our sound channel.
- theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)SoundCallBack);
- if (theErr != noErr) return; // Exit if that failed.
- }
- // Play the sound synchronously by passing FALSE.
- theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, FALSE);
- if (theErr != noErr) return; // Exit if that failed, but if it works,…
- // …wait right here while the sound plays.
- soundPriority = 0; // Establish globally that the sound is done playing.
- return(theErr);
- }
-
- // =========================================================================================\\
- // The Feeping Creature routines \\
- // =========================================================================================\\
- // "Creeping Featurism" is a threat to good programs. If you throw in everything but… \\
- // …the kitchen sink, pretty soon you have an abomination like MS Word 6.0. \\
- // Creeping featurism is particularly disgusting in games, many of which are so… \\
- // …complex as to be unplayable. \\
- // Use these routines with care. Sound should enhance, not overpower, your games. \\
- // =========================================================================================\\
-
- //-------------------------------------------------------------- PlayLoopSound
-
- // A pretty useful sound routine. It darn near deserves to be above the Feeping Creatures line.
- // Same as PlayASound except when PlayLoopSound encounters itself (or a sound with the same…
- // …soundPriority number) already playing (or as the most recent entry in the sound channel queu).
- // In that case, it calls FlushSoundSoon instead of FlushSound Now, and inserts the new sound…
- // …into the queu. The sound currently playing continues to play, followed immediately by the…
- // …soundID# passed to PlayLoopSound.
- // Useful when making repeated calls for background sounds, to avoid "stuttering" due to the…
- // …sound interrupting itself at inappropriate times or due to pausing between loops.
- // If you're looping a sound that's six ticks long, and you pass the sound ID# to PlayLoopSound…
- // and call it every five ticks, the sound will repeat itself seamlessly forever.
-
- OSErr PlayLoopSound(short soundID, short priority)
- {
- OSErr theErr;
- Handle theSoundHandle;
-
- if (! IsSoundOn()) return; // Exit if we are not to play sounds.
-
- if (priority < soundPriority) return; // Exit if a higher priority sound is playing.
-
- theSoundHandle = Get1Resource('snd ', soundID); // Get the sound (only from our resource fork).
- if (theSoundHandle == nil) return; // Exit if we couldn't get the sound.
-
- if (! soundChannel == nil) // If soundChannel is open, flush out any upcoming…
- FlushSoundSoon(); // … commands in the soundChannel queu.
- else
- { // Create our sound channel.
- theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)&SoundCallBack);
- if (theErr != noErr) return; // Exit if that failed.
- }
- // Play the sound asynchronously.
- theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, TRUE);
- if (theErr != noErr) return; // And exit if that failed.
-
- soundPriority = priority; // Establish globally the priority of the sound playing.
-
- InstallCallBack(); // Lastly, queue up a callBackCmd to notify us…
- // when the sound has finished playing.
- return(theErr);
- }
-
-
- //-------------------------------------------------------------- PlayLoopSoundOften
-
- // This stuffs soundChannel with a loop that repeats over and over and over and over again.
- // Use sparingly, or the player will get real real sick of it. Real real real real sick of it.
- // Note the sound channel holds a finite number of commands (128 I think), and this routine…
- // …will run synchronously (that is, play the sound over and over while everything else is…
- // …locked up) for any sounds the queu can't hold. I don't pass more than 100 howManyLoops,…
- // …but there's a commented-out check for that if you need it.
-
- OSErr PlayLoopSoundOften(short soundID, short priority, short howManyLoops)
- {
- OSErr theErr;
- Handle theSoundHandle;
- int count;
-
- if (! IsSoundOn()) return; // Exit if we are not to play sounds.
-
- if (priority < soundPriority) return; // Exit if a higher priority sound is playing.
-
- // if (howManyLoops > 100) // If an excessively large number is passed,…
- // howManyLoops = 100; // …reduce it so it won't overflow the queu.
-
- theSoundHandle = Get1Resource('snd ', soundID); // Get the sound (only from our resource fork).
- if (theSoundHandle == nil) return; // Exit if we couldn't get the sound.
-
- if (! soundChannel == nil) // If soundChannel is open, flush out everything…
- FlushSoundNow(); // …from the soundChannel command queu.
- else
- { // Create our sound channel.
- theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)&SoundCallBack);
- if (theErr != noErr) return; // Exit if that failed.
- }
- for (count = 1; count <= howManyLoops; count++)
- { // Stuff the sound queu with sounds to play,…
- theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, TRUE);
- if (theErr != noErr) return; // …and exit if that failed.
- }
- soundPriority = priority; // Establish globally the priority of the sound playing.
-
- InstallCallBack(); // Lastly, queue up a callBackCmd to notify us…
- // …when the sound has finished playing.
- return(theErr);
- }
-
- //-------------------------------------------------------------- AddSoundToQ
-
- // AddSoundToQ does not flush the command queue from soundChannel, nor silence soundChannel, nor…
- // …check the priority of the sound currently playing, nor establish its own priority.
- // What it does is add (a command to play the selected sound) to the end of the soundChannel…
- // …command queu, and set soundPriority to zero.
- // Any call to any other Play…Sound routine will interrupt AddSoundToQ, and…
- // …AddSoundToQ will add its sound to the queu created by any Play…Sound routine. This routine…
- // …resides outside the entire soundPriority system, and must be called with care.
- // So what's AddSoundToQ good for? Stringing a series of sounds together. Put individual words…
- // in resource files, and play them back as sentences. Record assorted barnyard animal sounds,…
- // …sort them by pitch, store them as 'snd ' resources, and play them back as music.
- // The standard sound queu holds 128 commands; that's enough for some quite complex dialog…
- //…from ground control, or for a flock of sheep to baa a full chorus of "In the Mood," but don't…
- // …go over 128, or it'll play synchronously (that is, everything but the sound will lock up)…
- // …until the queue works its way back down to 128 commands.
-
- void AddSoundToQ (short soundID)
- {
- OSErr theErr;
- Handle theSoundHandle;
-
- if (! IsSoundOn()) return; // Exit if we are not to play sounds.
-
- theSoundHandle = Get1Resource('snd ', soundID); // Get the sound (only from our resource fork).
- if (theSoundHandle == nil) return; // Exit if we couldn't get the sound.
-
- if ( soundChannel == nil) // If there's no soundChannel, open it.
- theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)&SoundCallBack);
- if (theErr != noErr) return; // Exit if that failed.
-
- // Add this sound to the the command queue.
- theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, TRUE);
-
- soundPriority = 0; // Set it here instead of using the sound callback…
- // …routine--saves queue space.
- }
-
- //------------------------------------------------------------------------------------------\\
- // End MGWSound1.c \\
- //------------------------------------------------------------------------------------------\\
-